package com.sunday.common.http.apache.sync;

import com.sunday.common.core.constants.LogConstants;
import com.sunday.common.core.gson.GsonUtils;
import com.sunday.common.http.apache.impl.DefaultConnectionKeepAliveStrategy;
import com.sunday.common.http.apache.sentinel.SentinelApacheHttpClientBuilder;
import com.sunday.common.http.apache.sentinel.config.SentinelApacheHttpClientConfig;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

/**
 * HttpClientPoolBuilder 类是一个构建器类，用于创建一个与阿里巴巴的Sentinel库整合，用于限流和熔断器的实现的Apache Http客户端。
 * https://hc.apache.org/httpcomponents-client-5.2.x/index.html
 * https://github.com/apache/httpcomponents-client/blob/master/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHttpExchange.java
 *
 * @author zsy
 * @since 2024/1/19
 */
@Slf4j
public record HttpClientPoolBuilder(RequestConfig requestConfig, PoolingHttpClientConnectionManager connectionManager,
                                    SentinelApacheHttpClientConfig sentinelConfig) {

    /**
     * 缺省: http连接池中connection的存活时间 （复用在空闲连接巡检上）
     */
    private final static TimeValue MAX_IDLE_TIME = TimeValue.ofMinutes(1);

    public static HttpClientPoolBuilder create() {
        return create(RequestBuilder.create().requestConfig(), ConnectionManagerBuilder.create().connectionManager(), SentinelApacheHttpClientConfig.create());
    }

    public static HttpClientPoolBuilder create(RequestConfig requestConfig) {
        return create(requestConfig, ConnectionManagerBuilder.create().connectionManager(), SentinelApacheHttpClientConfig.create());
    }

    public static HttpClientPoolBuilder create(PoolingHttpClientConnectionManager connectionManager) {
        return create(RequestBuilder.create().requestConfig(), connectionManager, SentinelApacheHttpClientConfig.create());
    }

    public static HttpClientPoolBuilder create(SentinelApacheHttpClientConfig sentinelConfig) {
        return create(RequestBuilder.create().requestConfig(), ConnectionManagerBuilder.create().connectionManager(), sentinelConfig);
    }

    /**
     * 创建一个与阿里巴巴的Sentinel库整合，用于限流和熔断器的实现的Apache Http客户端
     *
     * @param requestConfig     请求配置的对象
     * @param connectionManager 连接管理器
     * @param sentinelConfig    sentinel配置
     * @return {@link HttpClientPoolBuilder}
     */
    public static HttpClientPoolBuilder create(RequestConfig requestConfig, PoolingHttpClientConnectionManager connectionManager, SentinelApacheHttpClientConfig sentinelConfig) {
        return new HttpClientPoolBuilder(requestConfig, connectionManager, sentinelConfig);
    }

    public HttpClientBuilder httpClientBuilder() {
        /** 进行sentinel能力添加 */
        HttpClientBuilder httpBuilder = new SentinelApacheHttpClientBuilder(sentinelConfig);
//        HttpClientBuilder httpBuilder = HttpClientBuilder.create()

        httpBuilder.setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                // 使这个Httpclient实例使用后台线程主动地从连接池中退出空闲连接.
                .evictExpiredConnections()
                .evictIdleConnections(MAX_IDLE_TIME);

        httpBuilder.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE);
        /**
         * 方法等效于
         * httpClient = HttpClients.createDefault(); -> return HttpClientBuilder.create().build();
         * HttpClient实现应该是线程安全的。建议将此类的同一实例重用于多个请求执行。
         * 当CloseableHttpClient不再需要某个实例并且该实例将超出范围时，必须通过调用该CloseableHttpClient#close() 方法关闭与该实例关联的连接管理器。
         * 尽管HttpClient实例是线程安全的，并且可以在多个执行线程之间共享，但强烈建议每个线程都维护自己的专用实例HttpContext
         */
        return httpBuilder;
    }

    /**
     * 连接管理器
     *
     * @author zsy
     * @since 2022/10/15
     */
    @Data
    @NoArgsConstructor
    public static class ConnectionManagerBuilder {
        /**
         * 每条路由的最大值
         * 每个服务（ip + port）的允许并发请求数量 route: {}-> http://127.0.0.1:14001] 路由策略为 ip:port 组成
         * 路由key是 ip和port
         * <p>
         * 特别注意下面两个值
         * 1.当请求到达 线程池最大数量时，会已队列的形式慢慢消费
         * 2.当每个服务到达上限，也会慢慢进行消费，这是一个双重锁
         */
        private int maxPerRoute = 1_00_00;
        //总计当前正在交互的http请求数量 [total kept alive: 1; route allocated: 1 of 1; total allocated: 2 of 2]
        //线程池总大小
        private int maxTotal = 10_00_00;
        //http连接池中connection的存活时间
        private TimeValue timeToLive = TimeValue.ofMinutes(1);

        public ConnectionManagerBuilder(TimeValue timeToLive) {
            this.timeToLive = timeToLive;
        }

        public static ConnectionManagerBuilder create() {
            return new ConnectionManagerBuilder();
        }

        public static ConnectionManagerBuilder create(TimeValue timeToLive) {
            return new ConnectionManagerBuilder(timeToLive);
        }

        /**
         * HttpClient使用一个特殊的实体来管理对HTTP连接的访问​​，该实体称为HTTP连接管理器，由HttpClientConnectionManager接口表示
         * BasicHttpClientConnectionManager
         * 是一个简单的连接管理器，一次仅维护一个连接。即使此类是线程安全的，也应仅由一个执行线程使用。 BasicHttpClientConnectionManager将努力将连接重新用于具有相同路由的后续请求。
         * 但是，如果持久连接的路由与连接请求的路由不匹配，它将关闭现有连接并针对给定的路由重新打开它。如果已经分配了连接，则 java.lang.IllegalStateException抛出该连接。
         * 此连接管理器实现应在EJB容器内使用。
         * PoolingHttpClientConnectionManager
         * 是一个更复杂的实现，它管理客户端连接池并能够为来自多个执行线程的连接请求提供服务。连接是基于每个路由池的。通过在池中租用连接而不是创建全新的连接，可以为管理器在池中已经具有持久连接的路由请求提供服务
         * 在每个路由上总共维护一个最大连接数限制。默认情况下，此实现将为每个给定路由创建不超过2个并发连接，并且总共不超过20个连接。对于许多现实应用程序，这些限制可能证明过于严格，特别是如果它们使用HTTP作为其服务的传输协议
         */
        public PoolingHttpClientConnectionManager connectionManager() {
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry(), null, timeToLive, null);
            connectionManager.setMaxTotal(maxTotal);
            connectionManager.setDefaultMaxPerRoute(maxPerRoute);
            return connectionManager;
        }

        public Registry socketFactoryRegistry() {
            return RegistryBuilder.<ConnectionSocketFactory>create()
                    //PlainConnectionSocketFactory是用于创建和初始化普通（未加密）套接字的默认工厂
                    .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.INSTANCE)
                    //SSLConnectionSocketFactory -> LayeredConnectionSocketFactory是ConnectionSocketFactory接口的扩展。分层套接字工厂能够创建在现有普通套接字上分层的套接字。套接字分层主要用于通过代理创建安全套接字。HttpClient附带了SSLSocketFactory实现SSL / TLS分层的功能。请注意，HttpClient不使用任何自定义加密功能。它完全依赖于标准Java加密（JCE）和安全套接字（JSEE）扩展。
                    .register(URIScheme.HTTPS.id, new SSLConnectionSocketFactory(sslContext(), NoopHostnameVerifier.INSTANCE))
                    .build();
        }

        public SSLContext sslContext() {
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
                X509TrustManager tm = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                sslContext.init(null, new TrustManager[]{tm}, null);
            } catch (Throwable e) {
                log.error(LogConstants.error_format, e.toString(), e);
            }
            return sslContext;
        }

        @Override
        public String toString() {
            return GsonUtils.DEFAULT.toJson(this);
        }

    }

    /**
     * 缺省请求配置的对象
     *
     * @author zsy
     * @since 2022/10/15
     */
    @Data
    public static class RequestBuilder {
        /**
         * 连接建立时间，三次握手完成时间 10S
         */
        private Timeout connectTimeout = Timeout.ofSeconds(10);
        /**
         * httpClient使用连接池来管理连接，这个时间就是从连接池获取连接的超时时间，可以想象下数据库连接池 20S
         */
        private Timeout connectionRequestTimeout = Timeout.ofSeconds(20);
        /**
         * 数据传输过程中数据包之间间隔的最大时间 10S
         */
        private Timeout responseTimeout = Timeout.ofSeconds(10);


        public static RequestBuilder create() {
            return new RequestBuilder();
        }

        public RequestConfig requestConfig() {
            return RequestConfig.custom()
                    .setConnectTimeout(connectTimeout)
                    .setConnectionRequestTimeout(connectionRequestTimeout)
                    .setResponseTimeout(responseTimeout)
                    .build();
        }

        @Override
        public String toString() {
            return GsonUtils.DEFAULT.toJson(this);
        }

    }

}